﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.OleDb;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Collections.Concurrent;
using System.Data;

namespace Ogdi.Data.DataLoader
{
    // TODO: consider implementing IDisposable
    class CsvEntityProducer : EntityProducer
    {
        DbDataReader _dataReader;
        CsvEntityProducerParams _params;
        Entity _schemaEntity;
        bool _sourceOrder;

        public CsvEntityProducer(string fileSetName, string entitySet, string entityKind, CsvEntityProducerParams parameters, bool sourceOrder)
        {
            _dataReader = GetDataReader(fileSetName);
            _params = parameters;
            _schemaEntity = GetSchemaEntity(entitySet, entityKind, parameters.PropertyToTypeMap);
            _sourceOrder = sourceOrder;
        }
        
        //check that source data contains columns listed in metadata
        public override void ValidateParams()
        {
            if (_params.PropertyToTypeMap != null)
            {
                DataTable cvsSchema = _dataReader.GetSchemaTable();

                foreach (KeyValuePair<string,string> pair in _params.PropertyToTypeMap.GetProperties())
                {
                    DataRow[] rows = cvsSchema.Select(string.Format("ColumnName = '{0}'", pair.Key));
                    if (rows.Length == 0)
                    {
                        throw new ParamsValidationException(pair.Key);
                    }
                }
            }
            if (_params.PlacemarkParams != null)
            {
                _schemaEntity.ValidateProperty(_params.PlacemarkParams.LatitudeProperty);
                _schemaEntity.ValidateProperty(_params.PlacemarkParams.LongitudeProperty);
                if (_params.PlacemarkParams.NameProperties != null)
                {
                    for (int i = 0; i < _params.PlacemarkParams.NameProperties.Length; i++)
                    {
                        _schemaEntity.ValidateProperty(_params.PlacemarkParams.NameProperties[i]);
                    }
                }
            }
        }

        public override Entity SchemaEntity
        {
            get { return _schemaEntity; }
        }

        public override IEnumerable<Entity> GetEntitiesEnumerator(OnContinueExceptionCallback exceptionNotifier)
        {
            int count = 0;
            string initialTimePrefix = GetSecondsFrom2000Prefix();
            Dictionary<String, String> properties = _params.PropertyToTypeMap.GetProperties().ToDictionary(c=>c.Key.ToLower(), c=>c.Value);

            while (_dataReader.Read())
            {
                var entity = new Entity();
                bool isExceptionOccured = false;

                try
                {
                    for (int i = 0; i < _dataReader.FieldCount; ++i)
                    {
                        if (_dataReader[i].GetType() != typeof(System.DBNull))
                        {
                            var n = _dataReader.GetName(i);
                            try
                            {
                                if (!properties.ContainsKey(n.ToLower()))
                                    throw new ApplicationException("The row data is mismached with column definitions in data file.");

                                var v = GetPropertyValue(properties[n.ToLower()], _dataReader[i]);
                                entity.AddProperty(n, v);
                            }
                            catch (FormatException ex)
                            {
                                var sb = new StringBuilder();
                                sb.Append("Could not add property:");
                                sb.AppendFormat("\r\nName\t = '{0}'", n);
                                sb.AppendFormat("\r\nType\t = '{0}'", _params.PropertyToTypeMap[n]);
                                sb.AppendFormat("\r\nValue\t = '{0}'", _dataReader[i]);

                                exceptionNotifier(new EntityProcessingException(entity.ToString(), ex));
                            }

                        }
                    }
                    
                    if (_params.PlacemarkParams != null)
                    {
                        var longitude = _dataReader[_params.PlacemarkParams.LongitudeProperty];
                        var latitude = _dataReader[_params.PlacemarkParams.LatitudeProperty];

                        if (longitude.GetType() != typeof(System.DBNull) && latitude.GetType() != typeof(System.DBNull))
                        {
                            var lon = decimal.Parse(longitude.ToString());
                            var lat = decimal.Parse(latitude.ToString());

                            if (!(lon == 0 && lat == 0))
                            {
                                var placemark = new XElement(XNamespace.Get(DataLoaderConstants.NsKmlNew) + DataLoaderConstants.ElemNamePlacemark);

                                string[] names = new string[_params.PlacemarkParams.NameProperties.Length];

                                for (int i = 0; i < _params.PlacemarkParams.NameProperties.Length; i++)
                                {
                                    names[i] = _dataReader[_params.PlacemarkParams.NameProperties[i]].ToString();
                                }

                                placemark.Add(new XElement(
                                    XNamespace.Get(DataLoaderConstants.NsKmlNew) + DataLoaderConstants.ElemNameName,
                                    new XCData(string.Format(_params.PlacemarkParams.NamePropertyFormatString, names))));

                                var coords = new XElement(XNamespace.Get(DataLoaderConstants.NsKmlNew) + DataLoaderConstants.ElemNameCoordinates, lon.ToString() + "," + lat.ToString());
                                placemark.Add(new XElement(
                                    XNamespace.Get(DataLoaderConstants.NsKmlNew) + DataLoaderConstants.ElemNamePoint,
                                    coords));

                                placemark.Add(new XElement(XNamespace.Get(DataLoaderConstants.NsKmlNew) + DataLoaderConstants.ElemNameDescription, entity.Id));

                                var ps = placemark.ToString(SaveOptions.DisableFormatting).Replace(
                                                            DataLoaderConstants.NsKmlOld,
                                                            DataLoaderConstants.NsKmlNew);

                                entity.AddProperty(DataLoaderConstants.PropNameKmlSnippet, ps);
                            }
                        }
                    }
                    if (_sourceOrder)
                    {
                        entity.SetNumber(count, initialTimePrefix);
                    }
                }
                catch (Exception ex)
                {
                    exceptionNotifier(new EntityProcessingException(entity.ToString(), ex));
                    isExceptionOccured = true;
                }


                if (!isExceptionOccured)
                {
                    yield return entity;
                    count++;
                }
            }
        }

        private static Entity GetSchemaEntity(string entitySet, string entityKind, PropertyToTypeMapper mapper)
        {
            var entity = new Entity();
            entity.AddProperty(DataLoaderConstants.PropNameEntitySet, entitySet);
            entity.AddProperty(DataLoaderConstants.PropNameEntityKind, entityKind);

            foreach (var p in mapper.GetProperties())
            {
                entity.AddProperty(p.Key, GetPropertyType(p.Value));
            }

            return entity;
        }

        private static object GetPropertyValue(string type, object value)
        {
            if (type.ToLower().Equals("string"))
            {
                return value.ToString();
            }
            else if (type.ToLower().Equals("int32"))
            {
                return Int32.Parse(value.ToString());
            }
            else if (type.ToLower().Equals("int64"))
            {
                return Int64.Parse(value.ToString());
            }
            else if (type.ToLower().Equals("double"))
            {
                return double.Parse(value.ToString());
            }
            else if (type.ToLower().Equals("bool"))
            {
                return bool.Parse((string)value.ToString());
            }
            else if (type.ToLower().Equals("bool-0or1"))
            {
                return int.Parse(value.ToString()) == 1 ? true : false;
            }
            else if (type.ToLower().Equals("datetime"))
            {
                return DateTime.Parse((string)value.ToString());
            }
            else if (type.ToLower().Equals("datetime-yyyymmdd"))
            {
                var s = value.ToString().Trim();
                var y = int.Parse(s.Substring(0, 4));
                var m = int.Parse(s.Substring(4, 2));
                var d = int.Parse(s.Substring(6, 2));
                return new DateTime(y, m, d);
            }
            else
            {
                throw new ArgumentException(DataLoaderConstants.MsgUnsupportedType, type);
            }
        }

        private static string GetPropertyType(string type)
        {
            if (type.ToLower().Equals("string"))
            {
                return typeof(string).ToString();
            }
            else if (type.ToLower().Equals("int32"))
            {
                return typeof(Int32).ToString();
            }
            else if (type.ToLower().Equals("int64"))
            {
                return typeof(Int64).ToString();
            }
            else if (type.ToLower().Equals("double"))
            {
                return typeof(double).ToString();
            }
            else if (type.ToLower().Equals("bool"))
            {
                return typeof(bool).ToString();
            }
            else if (type.ToLower().Equals("bool-0or1"))
            {
                return typeof(Int32).ToString();
            }
            else if (type.ToLower().Equals("datetime"))
            {
                return typeof(DateTime).ToString();
            }
            else if (type.ToLower().Equals("datetime-yyyymmdd"))
            {
                return typeof(DateTime).ToString();
            }
            else
            {
                throw new ArgumentException(DataLoaderConstants.MsgUnsupportedType, type);
            }
        }
        
        private DbDataReader GetDataReader(string fileSetName)
        {
            string file = fileSetName + DataLoaderConstants.FileExtCsv;

            var builder = new OleDbConnectionStringBuilder();
            builder.DataSource = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            builder.Provider = "Microsoft.Jet.OLEDB.4.0";
            builder["Extended Properties"] = "text;HDR=Yes;FMT=Delimited;";

            var connection = new OleDbConnection(builder.ConnectionString);
            connection.Open();

            string query = string.Format(DataLoaderConstants.QueryRowCount, file);
            EntityCount = (int)new OleDbCommand(query, connection).ExecuteScalar();

            query = string.Format(DataLoaderConstants.QuerySelectAll, file);
            return new OleDbCommand(query, connection).ExecuteReader();
        }
    }
}
